/**
 * Clone native types.
 */
import {isObject, isPlainObject, kindOf} from "./type"

export function clone(val: any) {
	switch (kindOf(val)) {
		case 'Object': {
			if (isPlainObject(val)) {
				return Object.assign({}, val)
			} else {
				return val
			}
		}
		case 'Array': {
			return val.slice()
		}
		case 'RegExp': {
			let flags = ''
			if (val.multiline) flags += 'm'
			if (val.global) flags += 'g'
			if (val.ignorecase) flags += 'i'
			return new RegExp(val.source, flags)
		}
		case 'Date':
			return new Date(+val)
		default:
			return val
	}
}

/**
 * Todo
 * https://www.lodashjs.com/docs/lodash.cloneWith
 * @param val
 */
export function cloneWith(val: any, valCustomizer: Function): any {
	return clone(val)
}

/**
 * Recursively clone native types.
 */
function _deepCloneObject(source: any): any {
	if (isPlainObject(source)) {
		const out: any = {}
		for (const key in source) {
			if (source.hasOwnProperty(key)) {
				out[key] = deepClone(source[key])
			}
		}
		return out
	} else {
		return source
	}
}

function _deepCloneArray(array: any[]): any[] {
	const out: any[] = []
	let i = -1
	const n = array.length
	while (++i < n) out[i] = deepClone(array[i])
	return out
}

export function deepClone(val: any): any {
	switch (kindOf(val)) {
		case 'Object':
			return _deepCloneObject(val)
		case 'Array':
			return _deepCloneArray(val)
		default:
			return clone(val)
	}
}

/**
 * Todo
 * https://www.lodashjs.com/docs/lodash.cloneWith
 * @param val
 */
export function deepCloneWith(val: any, valCustomizer: Function): any {
	return deepClone(val)
}

/**
 * Deep assign(Object.assign) objects.
 */
export function deepAssign(target: any, ...objects: any[]) {
	target = deepClone(target)

	for (const obj of objects) {
		for (const key in obj) {
			if (obj.hasOwnProperty(key)) continue
			const val = obj[key]

			if (isObject(val) && isObject(target[key])) {
				// inception, deep merge objects
				target[key] = deepAssign(target[key], val)
			} else {
				// make sure arrays, regexp, date, objects are cloned
				target[key] = deepClone(val)
			}
		}
	}

	return target
}
